import service from '../../../browser/msg/service';
import byte_length from '../../../core/str/byte-length';
import _get_token from './_get_token';
import _set_token from './_set_token';

interface IMenuInfo {
	[key: string]: any;
}

class Service {
	private pnames: string[] = [];
	private operate_data: IMenuInfo[] = [];
	private query_flag = false;
	private clear_flag = false;
	private data: IMenuInfo[] = [];

	constructor(private spaceid: string) {
	}

	/**
	 * 查询菜单
	 */
	public query() {
		this.query_flag = true;
		return this;
	}

	/**
	 * 清空所有菜单
	 */
	public clear() {
		this.clear_flag = true;
		return this;
	}

	/**
	 * 新增点击菜单；点击推事件用户点击click类型按钮后，微信服务器会通过消息接口推送消息类型为event的结构给开发者（参考消息接口指南），并且带上按钮中开发者填写的key值，开发者可以通过自定义的key值与用户进行交互；
	 * @param name 菜单名称
	 * @param key key
	 * @param pname 父级菜单名称[可选参数]
	 */
	public add_click(name: string, key: string, pname?: string) {
		// 检查name的字节长度，不能超过11个字节
		const info = { type: 'click', name, key, pname };
		return this.check_add_edit_data(info);
	}

	/**
	 * 跳转URL用户点击view类型按钮后，微信客户端将会打开开发者在按钮中填写的网页URL，可与网页授权获取用户基本信息接口结合，获得用户基本信息。
	 * @param name 菜单名称
	 * @param url 页面地址
	 * @param pname 父级菜单名称[可选参数]
	 */
	public add_view(name: string, url: string, pname?: string) {
		// 检查name的字节长度，不能超过11个字节
		const info = { type: 'view', name, url, pname };
		return this.check_add_edit_data(info);
	}

	/**
	 * 小程序菜单
	 * @param name 菜单名称
	 * @param url 页面地址
	 * @param appid 小程序的appid
	 * @param pagepath 小程序的页面路径
	 * @param pname 父级菜单名称[可选参数]
	 */
	public add_miniprogram(name: string, url: string, appid: string, pagepath: string, pname?: string) {
		// 检查name的字节长度，不能超过11个字节
		const info = { type: 'miniprogram', name, url, appid, pagepath, pname };
		return this.check_add_edit_data(info);
	}

	/**
	 * 扫码推事件且弹出“消息接收中”提示框用户点击按钮后，微信客户端将调起扫一扫工具，完成扫码操作后，将扫码的结果传给开发者，同时收起扫一扫工具，然后弹出“消息接收中”提示框，随后可能会收到开发者下发的消息。
	 * @param name 菜单名称
	 * @param pname 父级菜单名称[可选参数]
	 */
	public add_scancode_waitmsg(name: string, key: string, pname?: string) {
		// 检查name的字节长度，不能超过11个字节
		const info = { type: 'scancode_waitmsg', name, key, pname };
		return this.check_add_edit_data(info);
	}

	/**
	 * 扫码推事件用户点击按钮后，微信客户端将调起扫一扫工具，完成扫码操作后显示扫描结果（如果是URL，将进入URL），且会将扫码的结果传给开发者，开发者可以下发消息。
	 * @param name 菜单名称
	 * @param pname 父级菜单名称[可选参数]
	 */
	public add_scancode_push(name: string, key: string, pname?: string) {
		// 检查name的字节长度，不能超过11个字节
		const info = { type: 'scancode_push', name, key, pname };
		return this.check_add_edit_data(info);
	}

	/**
	 * 弹出系统拍照发图用户点击按钮后，微信客户端将调起系统相机，完成拍照操作后，会将拍摄的相片发送给开发者，并推送事件给开发者，同时收起系统相机，随后可能会收到开发者下发的消息。
	 * @param name 菜单名称
	 * @param pname 父级菜单名称[可选参数]
	 */
	public add_pic_sysphoto(name: string, key: string, pname?: string) {
		// 检查name的字节长度，不能超过11个字节
		const info = { type: 'pic_sysphoto', name, key, pname };
		return this.check_add_edit_data(info);
	}

	/**
	 * 弹出微信相册发图器用户点击按钮后，微信客户端将调起微信相册，完成选择操作后，将选择的相片发送给开发者的服务器，并推送事件给开发者，同时收起相册，随后可能会收到开发者下发的消息。
	 * @param name 菜单名称
	 * @param pname 父级菜单名称[可选参数]
	 */
	public add_pic_weixin(name: string, key: string, pname?: string) {
		// 检查name的字节长度，不能超过11个字节
		const info = { type: 'pic_weixin', name, key, pname };
		return this.check_add_edit_data(info);
	}

	/**
	 * 弹出地理位置选择器用户点击按钮后，微信客户端将调起地理位置选择工具，完成选择操作后，将选择的地理位置发送给开发者的服务器，同时收起位置选择工具，随后可能会收到开发者下发的消息。
	 * @param name 菜单名称
	 * @param pname 父级菜单名称[可选参数]
	 */
	public add_location_select(name: string, key: string, pname?: string) {
		// 检查name的字节长度，不能超过11个字节
		const info = { type: 'location_select', name, key, pname };
		return this.check_add_edit_data(info);
	}

	/**
	 * 下发消息（除文本消息）用户点击media_id类型按钮后，微信服务器会将开发者填写的永久素材id对应的素材下发给用户，永久素材类型可以是图片、音频、视频、图文消息。请注意：永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
	 * @param name 菜单名称
	 * @param media_id 素材id， 调用新增永久素材接口返回的合法media_id
	 * @param pname 父级菜单名称[可选参数]
	 */
	public add_media_id(name: string, media_id: string, pname?: string) {
		// 检查name的字节长度，不能超过11个字节
		const info = { type: 'media_id', name, media_id, pname };
		return this.check_add_edit_data(info);
	}

	/**
	 * 跳转图文消息URL用户点击view_limited类型按钮后，微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL，永久素材类型只支持图文消息。请注意：永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
	 * @param name 菜单名称
	 * @param media_id 素材id， 调用新增永久素材接口返回的合法media_id
	 * @param pname 父级菜单名称[可选参数]
	 */
	public add_view_limited(name: string, media_id: string, pname?: string) {
		// 检查name的字节长度，不能超过11个字节
		const info = { type: 'view_limited', name, media_id, pname };
		return this.check_add_edit_data(info);
	}

	/**
	 * 弹出拍照或者相册发图用户点击按钮后，微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。
	 * @param name 菜单名称
	 * @param pname 父级菜单名称[可选参数]
	 */
	public add_pic_photo_or_album(name: string, key: string, pname?: string) {
		// 检查name的字节长度，不能超过11个字节
		const info = { type: 'pic_photo_or_album', name, key, pname };
		return this.check_add_edit_data(info);
	}

	/**
	 * 删除某一个菜单
	 * @param name 菜单名称
	 * @param pname 父级菜单名称[可选参数，传入删除该父级菜单下的某一个子级菜单，不传入则删除`name`值的一级菜单]
	 */
	public remove(name: string, pname?: string) {
		if (pname) {
			this.edit_data({ pname, name }, false);
			return this;
		} else {
			this.edit_data({ name }, false);
			return this;
		}
	}

	/**
	 * 执行
	 */
	public async exec() {
		const token = await _get_token(this.spaceid);
		const q_res = await this._query(token);
		if (this.query_flag) {
			this.query_flag = false;
			if (this.operate_data.length === 0) {
				return q_res;
			} else {
				// 数据处理
				return await this.operate(token, q_res);
			}
		} else if (this.clear_flag) {
			this.clear_flag = false;
			if (this.operate_data.length === 0) {
				const c_res = await this._remove(token);
				return c_res;
			} else {
				// 数据处理
				return await this.operate(token, []);
			}
		} else {
			if (this.operate_data.length === 0) {
				return { errcode: 0, errmsg: 'OK' };
			} else {
				return await this.operate(token, q_res);
			}
		}
	}

	private check_add_edit_data(info: IMenuInfo) {
		const pname = info.pname;
		const name = info.name;
		const url = info.url;
		if (pname && url) {
			if (byte_length(pname) <= 11 && byte_length(name) <= 42 && byte_length(url) <= 1024) {
				this.edit_data({ name: pname, sub_button: [] }, true);
				this.edit_data(info, true);
				return this;
			} else {
				throw Error('菜单标题，不超过11个字节，子菜单不超过42个字节；网页 链接，用户点击菜单可打开链接，不超过1024字节。');
			}
		} else if (pname) {
			if (byte_length(pname) <= 11 && byte_length(name) <= 42) {
				this.edit_data({ name: pname, sub_button: [] }, true);
				this.edit_data(info, true);
				return this;
			} else {
				throw Error('菜单标题，不超过11个字节，子菜单不超过42个字节；');
			}
		} else if (url) {
			if (byte_length(name) <= 11 && byte_length(url) <= 1024) {
				this.edit_data(info, true);
				return this;
			} else {
				throw Error('菜单标题，不超过11个字节；网页 链接，用户点击菜单可打开链接，不超过1024字节。');
			}
		} else {
			if (byte_length(name) <= 11) {
				this.edit_data(info, true);
				return this;
			} else {
				throw Error('菜单标题，不超过11个字节；');
			}
		}
	}

	private edit_data(menu_info: { [key: string]: any }, add: boolean) {
		if (menu_info.pname) {
			// 一级菜单
			const name = menu_info.name;
			if (!this.pnames.includes(name)) {
				this.pnames.push(name);
				this.operate_data.push([menu_info, add]);
			}
		} else {
			this.operate_data.push([menu_info, add]);
		}
	}

	private async operate(token: string, data: IMenuInfo[]) {
		this.data = data;
		this.operate_data.forEach((item) => {
			const menu_info = item[0];
			const flag = item[1];
			if (flag) {
				// 新增
				this.add(menu_info);
			} else {
				// 删除
				if (data.length > 0) {
					this.del(menu_info);
				}
			}
		});
		// 将操作数据清空
		this.operate_data = [];
		return await this._add(token);
	}

	private add(menu_info: IMenuInfo) {
		const pname = menu_info.pname;
		if (pname) {
			const idx = this.find_idx(pname, this.data);
			if (idx > -1) {
				const info: IMenuInfo[] = this.data[idx].sub_button;
				if (info) {
					info.push(menu_info);
				} else {
					this.data[idx].sub_button = [menu_info];
				}
			}
		} else {
			const name = menu_info.name;
			const idx = this.find_idx(name, this.data);
			if (idx === -1) {
				this.data.push(menu_info);
			}
		}
	}

	private del(menu_info: IMenuInfo) {
		const pname = menu_info.pname;
		if (pname) {
			const idx = this.find_idx(pname, this.data);
			if (idx > -1) {
				const name = menu_info.name;
				const info: IMenuInfo[] = this.data[idx].sub_button;
				const d_idx = this.find_idx(name, info);
				if (idx > -1) {
					info.splice(d_idx, 1);
					// 如果二级菜单全部删除，将默认也删除一级菜单
					if (info.length === 0) {
						this.data.splice(idx, 1);
					}
				}
			}
		} else {
			const name = menu_info.name;
			const idx = this.find_idx(name, this.data);
			if (idx > -1) {
				this.data.splice(idx, 1);
			}
		}
	}

	private find_idx(name: string, data: IMenuInfo[]) {
		return data.findIndex((item) => {
			return item.name === name;
		});
	}

	private async _add(token: string) {
		if (this.data.length === 0) {
			return await this._remove(token);
		}
		// 新增菜单
		const a_res = await this._service(token, { button: this.data }, 'create');
		if (a_res.errcode === 40001) {
			// token失效
			const t = await _set_token(this.spaceid);
			const res = await this._service(t, { button: this.data }, 'create');
			return res;
		} else {
			return a_res;
		}
	}


	private async _query(token: string) {
		const q_res = await this._service<{ errcode: number; errmsg: string; menu: { button: IMenuInfo[] } }>(token, {}, 'get');
		if (q_res.errcode === 0) {
			// 请求成功
			return q_res.menu.button;
		} else if (q_res.errcode === 40001) {
			// token失效
			const t = await _set_token(this.spaceid);
			const res = await this._service<{ errcode: number; errmsg: string; menu: { button: IMenuInfo[] } }>(t, {}, 'get');
			if (res.errcode === 0) {
				// 请求成功
				return res.menu.button;
			} else {
				throw Error(JSON.stringify(res));
			}
		} else {
			throw Error(JSON.stringify(q_res));
		}
	}

	private async _remove(token: string) {
		// 删除菜单
		const d_res = await this._service(token, {}, 'del');
		if (d_res.errcode === 40001) {
			// token失效
			const t = await _set_token(this.spaceid);
			const res = await this._service(t, {}, 'del');
			return res;
		} else {
			return d_res;
		}
	}

	private async _service<T extends { errcode: number; errmsg: string; }>(token: string, body: any, subactionflag: string) {
		// 菜单服务
		const q_param = {
			accessToken: token,
			body,
			serviceflag: 'menuservice',
			subactionflag
		};
		const q_res = await service<T>('thirdpart.weixinpublicno', q_param, false, false);
		if (q_res.errcode !== 0 && q_res.errcode !== 40001) {
			console.error(JSON.stringify(q_res));
			throw Error(q_res.errmsg);
		} else {
			return q_res;
		}
	}
}

/**
 * 菜单操作
 * @param spaceid 项目spaceid
 */
export default function _menu(spaceid: string) {
	return new Service(spaceid);
}
